package com.xbongbong.schedule.service.impl;

import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.constant.TimeConstant;
import com.xbongbong.paas.enums.DelEnum;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.BeanUtil;
import com.xbongbong.paas.toolbox.util.CollectionsUtil;
import com.xbongbong.paas.toolbox.util.DateTimeUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.paas.toolbox.wrap.XbbResponse;
import com.xbongbong.pro.enums.ErrorCodeEnum;
import com.xbongbong.pro.enums.errorcodes.DelayTaskErrorCodeEnum;
import com.xbongbong.schedule.domain.dao.ScheduleTaskDao;
import com.xbongbong.schedule.domain.dao.ScheduleTaskRelativeBusinessDao;
import com.xbongbong.schedule.domain.entity.ScheduleTaskEntity;
import com.xbongbong.schedule.domain.entity.ScheduleTaskRelateBusinessEntity;
import com.xbongbong.schedule.enums.ScheduleTaskStatusEnum;
import com.xbongbong.schedule.enums.ScheduleTaskTypeEnum;
import com.xbongbong.schedule.pojo.ScheduleTaskRelateBusinessPOJO;
import com.xbongbong.schedule.pojo.dto.ScheduleTaskActionDTO;
import com.xbongbong.schedule.pojo.dto.ScheduleTaskAddBatchDTO;
import com.xbongbong.schedule.pojo.dto.ScheduleTaskAddDTO;
import com.xbongbong.schedule.pojo.dto.ScheduleTaskCancelDTO;
import com.xbongbong.schedule.pojo.dto.ScheduleTaskRabbitmqDTO;
import com.xbongbong.schedule.pojo.vo.ScheduleTaskActionVO;
import com.xbongbong.schedule.pojo.vo.ScheduleTaskAddBatchVO;
import com.xbongbong.schedule.pojo.vo.ScheduleTaskAddVO;
import com.xbongbong.schedule.producer.ScheduleTaskProducer;
import com.xbongbong.schedule.service.ScheduleTaskService;
import com.xbongbong.schedule.task.ScheduleTaskContext;
import com.xbongbong.schedule.util.ScheduleTaskTableUtil;
import com.xbongbong.schedule.util.SystemEnvHelp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@Service("scheduleTaskService")
public class ScheduleTaskServiceImpl implements ScheduleTaskService {

    private static final Logger LOG = LoggerFactory.getLogger(ScheduleTaskService.class);

    @Resource
    private ScheduleTaskDao scheduleTaskDao;
    @Resource
    private ScheduleTaskRelativeBusinessDao scheduleTaskRelativeBusinessDao;
    @Resource
    private ScheduleTaskProducer scheduleTaskProducer;

    @Resource
    private ScheduleTaskContext scheduleTaskContext;

    @Resource
    private DataSourceProperties dataSourceProperties;

    @Resource
    private SystemEnvHelp systemEnvHelp;

    @Override
    public boolean isTableExist(String tableName) {
        String tableSchema = getTableSchema();
        return scheduleTaskDao.isTableExist(tableName,tableSchema) > 0;
    }

    private String getTableSchema() {
        String tableSchema = null;
        try {
            String url = dataSourceProperties.getUrl();
            String[] splits = url.split("/");
            String lastStr = splits[splits.length -1];
            tableSchema = lastStr.substring(0,lastStr.indexOf("?"));
        } catch (Exception e) {
            LOG.error("获取tableSchema出错:",e);
        }
        return tableSchema;
    }

    @Override
    public String createTable(String tableName) {
        if (!isTableExist(tableName)) {
            scheduleTaskDao.createTable(tableName);
        }
        return tableName;
    }

    @Override
    public ScheduleTaskAddVO addScheduleTask(ScheduleTaskAddDTO scheduleTaskAddDTO) {
        String tableName = ScheduleTaskTableUtil.getTableName(scheduleTaskAddDTO.getTriggerTime());
        createTable(tableName);
        ScheduleTaskEntity scheduleTaskEntity = new ScheduleTaskEntity();
        BeanUtil.copyProperties(scheduleTaskAddDTO, scheduleTaskEntity);
        scheduleTaskEntity.setName(ScheduleTaskTypeEnum.getMemoByType(scheduleTaskEntity.getScheduleType()));
        scheduleTaskDao.insert(tableName, scheduleTaskEntity);
        ScheduleTaskAddVO scheduleTaskAddVO = new ScheduleTaskAddVO();
        BeanUtil.copyProperties(scheduleTaskEntity, scheduleTaskAddVO);

        ScheduleTaskRelateBusinessPOJO scheduleTaskRelateBusinessPOJO = scheduleTaskAddDTO.getScheduleTaskRelateBusiness();
        ScheduleTaskRelateBusinessEntity scheduleTaskRelateBusinessEntity = new ScheduleTaskRelateBusinessEntity();
        BeanUtil.copyProperties(scheduleTaskRelateBusinessPOJO, scheduleTaskRelateBusinessEntity);
        scheduleTaskRelateBusinessEntity.setCorpid(scheduleTaskEntity.getCorpid());
        scheduleTaskRelateBusinessEntity.setFormId(scheduleTaskEntity.getFormId());
        scheduleTaskRelateBusinessEntity.setScheduleType(scheduleTaskEntity.getScheduleType());
        scheduleTaskRelateBusinessEntity.setScheduleTaskId(scheduleTaskEntity.getId());
        scheduleTaskRelateBusinessEntity.setScheduleTaskTable(tableName);
        scheduleTaskRelativeBusinessDao.insert(scheduleTaskRelateBusinessEntity);

        return scheduleTaskAddVO;
    }

    @Override
    public ScheduleTaskAddBatchVO addBatchScheduleTask(ScheduleTaskAddBatchDTO scheduleTaskAddBatchDTO) {
        List<ScheduleTaskEntity> scheduleTaskEntityList = new ArrayList<>();
        List<ScheduleTaskRelateBusinessEntity> scheduleTaskRelateBusinessEntityList = new ArrayList<>();
        HashMap<String, List<ScheduleTaskEntity>> tableNameScheduleTaskListMap = new HashMap<>();

        List<ScheduleTaskAddDTO> scheduleTaskAddDTOList = scheduleTaskAddBatchDTO.getScheduleTaskList();
        scheduleTaskAddDTOList.forEach(item -> {
            String tableName = ScheduleTaskTableUtil.getTableName(item.getTriggerTime());
            //填写时间提醒调度任务信息
            ScheduleTaskEntity scheduleTaskEntity = new ScheduleTaskEntity();
            BeanUtil.copyProperties(item, scheduleTaskEntity);
            scheduleTaskEntity.setName(ScheduleTaskTypeEnum.getMemoByType(scheduleTaskEntity.getScheduleType()));
            //为了与后边scheduleTaskRelateBusinessEntityList 顺序匹配
            scheduleTaskEntityList.add(scheduleTaskEntity);
            //根据表名为触发任务分组
            if (tableNameScheduleTaskListMap.containsKey(tableName)) {
                tableNameScheduleTaskListMap.get(tableName).add(scheduleTaskEntity);
            } else {
                List<ScheduleTaskEntity> scheduleTaskList = new ArrayList<>();
                scheduleTaskList.add(scheduleTaskEntity);
                tableNameScheduleTaskListMap.put(tableName, scheduleTaskList);
            }

            ScheduleTaskRelateBusinessPOJO scheduleTaskRelateBusinessPOJO = item.getScheduleTaskRelateBusiness();
            ScheduleTaskRelateBusinessEntity scheduleTaskRelateBusinessEntity = new ScheduleTaskRelateBusinessEntity();
            BeanUtil.copyProperties(scheduleTaskRelateBusinessPOJO, scheduleTaskRelateBusinessEntity);
            scheduleTaskRelateBusinessEntity.setCorpid(scheduleTaskEntity.getCorpid());
            scheduleTaskRelateBusinessEntity.setFormId(scheduleTaskEntity.getFormId());
            scheduleTaskRelateBusinessEntity.setScheduleType(scheduleTaskEntity.getScheduleType());
            scheduleTaskRelateBusinessEntity.setScheduleTaskTable(tableName);
            scheduleTaskRelateBusinessEntityList.add(scheduleTaskRelateBusinessEntity);
        });
        //根据分组插入
        for (Map.Entry<String, List<ScheduleTaskEntity>> entry : tableNameScheduleTaskListMap.entrySet()) {
            LOG.info("批量插入ScheduleTask数据");
            createTable(entry.getKey());
            scheduleTaskDao.insertBatch(entry.getKey(), entry.getValue());
        }
        //将通过插入scheduleTask表后返回的id 填入scheduleTaskRelateBusinessEntity中
        for (int i = 0; i < scheduleTaskRelateBusinessEntityList.size(); ++i) {
            scheduleTaskRelateBusinessEntityList.get(i).setScheduleTaskId(scheduleTaskEntityList.get(i).getId());
        }
        if (CollectionsUtil.isNotEmpty(scheduleTaskRelateBusinessEntityList)) {
            LOG.info("批量插入ScheduleTaskRelateBusiness数据");
            scheduleTaskRelativeBusinessDao.insertBatch(scheduleTaskRelateBusinessEntityList);
        }

        ScheduleTaskAddBatchVO scheduleTaskAddBatchVO = new ScheduleTaskAddBatchVO();
        scheduleTaskAddBatchVO.setScheduleTaskEntityList(scheduleTaskEntityList);
        return scheduleTaskAddBatchVO;
    }

    @Override
    public void cancelScheduleTask(ScheduleTaskCancelDTO scheduleTaskCancelDTO) throws XbbException {
        Map<String, Object> param = BeanUtil.convertBean2Map(scheduleTaskCancelDTO, true);
        param.put("del", DelEnum.NORMAL.getDel());
        List<ScheduleTaskRelateBusinessEntity> scheduleTaskRelateBusinessList = scheduleTaskRelativeBusinessDao.findEntitys(param);
        Map<String, List<ScheduleTaskRelateBusinessEntity>> scheduleTaskRelateBusinessMap = scheduleTaskRelateBusinessList.stream()
                .collect(Collectors.groupingBy(ScheduleTaskRelateBusinessEntity::getScheduleTaskTable));

        scheduleTaskRelateBusinessMap.forEach((tableName, scheduleTaskRelateBusinessEntities) -> {
            List<Long> scheduleTaskIdIn = scheduleTaskRelateBusinessEntities.stream().map(ScheduleTaskRelateBusinessEntity::getScheduleTaskId).collect(Collectors.toList());
            Map<String, Object> taskMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
            taskMap.put("corpid", scheduleTaskCancelDTO.getCorpid());
            taskMap.put("formId", scheduleTaskCancelDTO.getFormId());
            taskMap.put("statusIn", Arrays.asList(ScheduleTaskStatusEnum.NONQUEUED.getCode(), ScheduleTaskStatusEnum.QUEUED.getCode()));
            taskMap.put("scheduleType", scheduleTaskCancelDTO.getScheduleType());
            taskMap.put("del", DelEnum.NORMAL.getDel());
            taskMap.put("idIn", scheduleTaskIdIn);
            // 将任务的状态设置为【取消】-- 包含未入列时取消 和 已入列时取消
            // ScheduleTaskStatusEnum.CANCEL_NONQUEUED或者ScheduleTaskStatusEnum.CANCEL_QUEUED
            scheduleTaskDao.cancelScheduleTask(tableName, taskMap);
            // 将任务与业务的关联关系删除
            List<Long> idIn = scheduleTaskRelateBusinessEntities.stream().map(ScheduleTaskRelateBusinessEntity::getId).collect(Collectors.toList());
            scheduleTaskRelativeBusinessDao.deleteByIdIn(scheduleTaskCancelDTO.getCorpid(), idIn);
        });
    }

    @Override
    public void updateScheduleTask(ScheduleTaskEntity scheduleTaskEntity) throws XbbException {
        String tableName = ScheduleTaskTableUtil.getTableName(scheduleTaskEntity.getTriggerTime());
        if (!isTableExist(tableName)) {
            throw new XbbException(DelayTaskErrorCodeEnum.API_ERROR_410001);
        }
        scheduleTaskDao.update(tableName, scheduleTaskEntity);
    }

    @Override
    public void roundPollingScheduleTask() {
        long currentTime = DateTimeUtil.getInt();
        Long triggerTimeEnd = currentTime + TimeConstant.SECONDS_PER_HOUR;
        String tableName = ScheduleTaskTableUtil.getTableName(currentTime);
        LOG.info("轮询开始时间:{}, Round Polling ScheduleTask Table:{}", DateTimeUtil.getString(), tableName);
        if (!isTableExist(tableName)) {
            // 轮询的时候表还没有创建，数据还没有生成
            return;
        }
        /*
            是否开启灰度环境【目前该服务访问不了主数据库，所以现在拿不到该配置】
            boolean isGrayEnvironmentEnable = sysEnvHelp.isGrayEnvironmentEnable();
        */
        // 当前实例是否灰度
        boolean isGray = systemEnvHelp.isGrayEnvironment();
        // 获取一小时内即将触发的延时任务
        Map<String, Object> param = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        param.put("status", ScheduleTaskStatusEnum.NONQUEUED.getCode());
        param.put("triggerTimeEnd", triggerTimeEnd);
        param.put("del", DelEnum.NORMAL.getDel());
        param.put("pageSize", 1000);
        param.put("orderByStr", "id asc");
        Long idGte = 0L;
        while (true) {
            param.put("idGte", idGte);
            List<ScheduleTaskEntity> scheduleTaskList = scheduleTaskDao.findEntitys(tableName, param);
            if (scheduleTaskList.isEmpty()) {
                break;
            }
            idGte = scheduleTaskList.get(scheduleTaskList.size() - 1).getId();
            List<Long> updateIdIn = new ArrayList<>();
            scheduleTaskList.forEach(task -> {
                boolean isGrayCompany = systemEnvHelp.checkIsCrayCompany(task.getCorpid());
                if (isGray && !isGrayCompany) {
                    // 灰度实例，任务所在公司不是灰度公司，不处理
                    return;
                } else if (!isGray && isGrayCompany) {
                    // 普通实例，任务所在公司是灰度公司，不处理
                    return;
                }
                ScheduleTaskRabbitmqDTO scheduleTaskRabbitmqDTO = new ScheduleTaskRabbitmqDTO(task);
                try {
                    if (task.getTriggerTime() <= currentTime) {
                        scheduleTaskProducer.addScheduleTaskImmediately(scheduleTaskRabbitmqDTO);
                        LOG.info("addScheduleTaskImmediately-轮询时间{}，表{}的数据{}加入队列成功", currentTime, tableName, task.getId());
                    } else {
                        // 将延时任务发送到延时队列
                        scheduleTaskProducer.addScheduleTask(scheduleTaskRabbitmqDTO);
                        LOG.info("addScheduleTask-轮询时间{}，表{}的数据{}加入队列成功", currentTime, tableName, task.getId());
                    }
                    updateIdIn.add(task.getId());
                } catch (Exception e) {
                    LOG.error("roundPollingScheduleTask-轮询时间{}，表{}的数据{}加入队列失败，失败原因{}", currentTime, tableName, task.getId(), e.getMessage());
                }
            });
            // 将延时任务的状态修改为【已入列】
            // ScheduleTaskStatusEnum.QUEUED
            scheduleTaskDao.queuedScheduleTaskByIdIn(tableName, updateIdIn);
        }
    }

    @Override
    public void handleScheduleTask(ScheduleTaskRabbitmqDTO scheduleTaskRabbitmqDTO) {
        ScheduleTaskEntity scheduleTaskEntity = scheduleTaskRabbitmqDTO.getScheduleTaskEntity();
        Long triggerTime = scheduleTaskEntity.getTriggerTime();
        String tableName = ScheduleTaskTableUtil.getTableName(triggerTime);
        Long scheduleTaskId = scheduleTaskEntity.getId();
        scheduleTaskEntity = scheduleTaskDao.getByKey(tableName, scheduleTaskId, scheduleTaskEntity.getCorpid());
        if (Objects.isNull(scheduleTaskEntity) || Objects.equals(scheduleTaskEntity.getDel(), DelEnum.DELETE.getDel())) {
            LOG.info("id={}的ScheduleTask数据不存在或者已删除，不需要进行消费端处理", scheduleTaskId);
            return;
        }
        // 获取任务的最新状态，跳过【已触发】或【已取消】任务
        if (ScheduleTaskStatusEnum.noNeedConsumeStatus().contains(scheduleTaskEntity.getStatus())) {
            LOG.info("id={}的ScheduleTask当前的状态={}，不需要进行消费端处理", scheduleTaskEntity.getId(), scheduleTaskEntity.getStatus());
            return;
        }
        Map<String, Object> param = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        param.put("corpid", scheduleTaskEntity.getCorpid());
        param.put("formId", scheduleTaskEntity.getFormId());
        param.put("scheduleTaskId", scheduleTaskEntity.getId());
        param.put("scheduleTaskTable", tableName);
        param.put("del", 0);
        List<ScheduleTaskRelateBusinessEntity> relateBusinessList = scheduleTaskRelativeBusinessDao.findEntitys(param);
        if (CollectionsUtil.isNotEmpty(relateBusinessList)) {
            ScheduleTaskRelateBusinessEntity scheduleTaskRelateBusinessEntity = relateBusinessList.get(0);
            ScheduleTaskActionDTO scheduleTaskActionDTO = new ScheduleTaskActionDTO(scheduleTaskEntity, scheduleTaskRelateBusinessEntity);
            scheduleTaskActionDTO.setUserId("system");
            scheduleTaskActionDTO.setCorpid("system");
            scheduleTaskActionDTO.setPlatform("web");
            try {
                XbbResponse<ScheduleTaskActionVO> response = scheduleTaskContext.scheduleTaskAction(scheduleTaskActionDTO, LocaleContextHolder.getLocale().toString());
                if (Objects.equals(response.getCode(), ErrorCodeEnum.API_SUCCESS.getCode())) {
                    // 将延时任务的状态修改为【已触发-成功】
                    // ScheduleTaskStatusEnum.TRIGGERED_SUCCESS
                    scheduleTaskDao.triggeredById(tableName, scheduleTaskEntity.getCorpid(), scheduleTaskEntity.getId(), ScheduleTaskStatusEnum.TRIGGERED_SUCCESS.getCode());
                } else {
                    // 将延时任务的状态修改为【已触发-失败】
                    // ScheduleTaskStatusEnum.TRIGGERED_FAIL
                    scheduleTaskDao.triggeredById(tableName, scheduleTaskEntity.getCorpid(), scheduleTaskEntity.getId(), ScheduleTaskStatusEnum.TRIGGERED_FAIL.getCode());
                    LOG.error("执行延时任务动作scheduleTaskAction失败，失败数据{}-{}-{}，失败原因{}", tableName, scheduleTaskEntity.getCorpid(), scheduleTaskEntity.getId(), response.getMsg());
                }
            } catch (Exception e) {
                LOG.error("延时任务执行动作异常",e);
            }
        }
    }

}